package hu.mapro.model.generator;

import hu.mapro.model.Getter;
import hu.mapro.model.Setter;
import hu.mapro.model.impl.Cardinality;
import hu.mapro.model.impl.LabeledField;
import hu.mapro.model.impl.ReadWriteField;
import hu.mapro.model.impl.ReadableField;
import hu.mapro.model.meta.BuiltinType;
import hu.mapro.model.meta.BuiltinTypes;
import hu.mapro.model.meta.ComplexType;
import hu.mapro.model.meta.DefinedType;
import hu.mapro.model.meta.EntityType;
import hu.mapro.model.meta.EnumType;
import hu.mapro.model.meta.Field;
import hu.mapro.model.meta.MetaUtils;
import hu.mapro.model.meta.PluralField;
import hu.mapro.model.meta.SingularField;
import hu.mapro.model.meta.Type;
import hu.mapro.model.meta.Type.TypeCategory;
import hu.mapro.model.meta.ValueType;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Function;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JAnnotationArrayMember;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JArray;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JClassContainer;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JSuperTypeWildcard;
import com.sun.codemodel.JType;
import com.sun.codemodel.JTypeVar;
import com.sun.codemodel.JVar;

@SuppressWarnings("rawtypes")
public class AutoBeanGenerator implements BeanMetadataGenerator {

	JCodeModel cm;
	
	public AutoBeanGenerator(JCodeModel cm) {
		super();
		this.cm = cm;
	}

	@Override
	public void generateFields(
			final OutputStream out,
			String packageName, String className,
			Annotater annotater, JClass serviceClass,
			Collection<DefinedType<?>> beanInfos, ExtraInfo extraInfo
	) {
		doGenerate(packageName, className, annotater, serviceClass, beanInfos, extraInfo);

		CodeWriter writer = new CodeWriter() {
			
			@Override
			public OutputStream openBinary(JPackage pkg, String fileName)
					throws IOException {
				return out;
			}
			
			@Override
			public void close() throws IOException {
				out.close();
			}
		};

		try {
			cm.build(writer);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		
	}

	public void generateFields(
			File destDirectory,
			String packageName, 
			String className,
			Annotater annotater, 
			JClass serviceClass,
			Collection<DefinedType<?>> beanInfos, 
			ExtraInfo extraInfo
	) {
		doGenerate(packageName, className, annotater, serviceClass, beanInfos, extraInfo);
		
		
		try {
			cm.build(destDirectory);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		
	}

	public interface TypeInfo {
		
		String getClassFullName();
		ComplexType getSuperClass();
		
		
	}
	public interface DefinedTypeInfo extends TypeInfo {
		ClassCustomizer getCustomizer();
	}
	public interface FieldInfo {
		String getReadMethod();
		String getWriteMethod();
	}
	
	public interface ExtraInfo {
		
		TypeInfo getInfo(Type type);
		DefinedTypeInfo getInfo(DefinedType type);
		
		FieldInfo getInfo(Field field);
	}
	
	class Generation {
		//String packageName; 
		//String className;
		//Annotater annotater; 
		JClassContainer classContainer;
		JClass serviceClass;
		Collection<DefinedType<?>> beanInfos;
		ExtraInfo extraInfo;
		
		
		
		public Generation(JClassContainer classContainer, JClass serviceClass,
				Collection<DefinedType<?>> beanInfos, ExtraInfo extraInfo) {
			super();
			this.classContainer = classContainer;
			this.serviceClass = serviceClass;
			this.beanInfos = beanInfos;
			this.extraInfo = extraInfo;
		}

		//JDefinedClass factoryAB;
		
		//JAnnotationArrayMember factoryCategory;
		//private JClass autoBeanClass;
		
		Set<String> domainClasses = Sets.newHashSet();
		private JDefinedClass factoryInit;
		private JFieldVar factoryInitField;
		private JAnnotationArrayMember requestFactoryExtraTypes;
		private JDefinedClass visitorsClass;
		private JBlock visitorsInit;
		private JFieldVar visitorHierarchy;
		private JDefinedClass factory;
		private JDefinedClass factoryByClass;
		private JFieldVar factoryByClassField;
		private JDefinedClass typesClass;
		private JDefinedClass fieldBaseInterface;
		private JDefinedClass fieldBaseReadbleInterface;
		private JDefinedClass fieldBaseReadWriteInterface;
		private JDefinedClass fieldBaseReadWriteSingularInterface;
		
		private void doGenerate(
		) throws JClassAlreadyExistsException {
		
			dc = classContainer;
			utilClass = dc._class(JMod.STATIC|JMod.PUBLIC, "Util");
			
			for (DefinedType bi : beanInfos) {
				domainClasses.add(extraInfo.getInfo(bi).getClassFullName());
			}

			
//			factoryAB = dc._interface(
//					JMod.PUBLIC, 
//					"FactoryAB"
//			);
//			factoryAB._implements(cm.directClass("com.google.web.bindery.autobean.shared.AutoBeanFactory"));
//			factoryCategory = factoryAB.annotate(cm.ref("com.google.web.bindery.autobean.shared.AutoBeanFactory.Category")).paramArray("value");

			factory = dc._interface(
					JMod.PUBLIC, 
					"Factory"
			);

			
			requestFactory = dc._interface(
					JMod.PUBLIC, 
					"RequestFactory"
			);
			requestFactory._implements(cm.directClass("com.google.web.bindery.requestfactory.shared.RequestFactory"));
			requestFactoryExtraTypes = requestFactory.annotate(cm.ref("com.google.web.bindery.requestfactory.shared.ExtraTypes")).paramArray("value");
			
			
			visitorsClass = dc._class(
					JMod.PUBLIC|JMod.STATIC, 
					"Visitors"
			);
			JClass visitorHierarchyType = cm.directClass("hu.mapro.model.VisitorHierarchy").narrow(Object.class);
			visitorHierarchy = visitorsClass.field(JMod.STATIC|JMod.FINAL, visitorHierarchyType, "_hierarchy", JExpr._new(visitorHierarchyType));
			visitorsInit = visitorsClass.init();

			factoryInit = dc._class(
					JMod.PUBLIC|JMod.STATIC, 
					"FactoryInit"
			);
			factoryInit._implements(factory);
			factoryInitField = factoryInit.field(JMod.PRIVATE|JMod.FINAL, factory, "_f");
			JMethod factoryBeanConstructor = factoryInit.constructor(JMod.PUBLIC);
			JVar factoryBeanConstructorParam = factoryBeanConstructor.param(factory, "factory");
			factoryBeanConstructor.body().assign(factoryInitField, factoryBeanConstructorParam);

			factoryByClass = dc._class(
					JMod.PUBLIC|JMod.STATIC, 
					"FactoryByClass"
			);
			factoryByClass._implements(factory);
			JClass classDataFactory = cm.directClass("hu.mapro.gwtui.shared.data.ClassDataFactory");
			factoryByClassField = factoryByClass.field(JMod.PRIVATE|JMod.FINAL, classDataFactory, "_f");
			JMethod factoryBCConstructor = factoryByClass.constructor(JMod.PUBLIC);
			JVar factoryBCConstructorParam = factoryBCConstructor.param(classDataFactory, "factory");
			factoryBCConstructor.body().assign(factoryByClassField, factoryBCConstructorParam);

			
			valueTypeVisitor = dc._interface(JMod.PUBLIC, "ValueTypeFieldVisitor");
			valueTypeVisitorGeneric = valueTypeVisitor.generify("T");
			entityTypeVisitor = dc._interface(JMod.PUBLIC, "EntityTypeFieldVisitor");
			entityTypeVisitorGeneric = entityTypeVisitor.generify("T");
			enumTypeVisitor = dc._interface(JMod.PUBLIC, "EnumerationTypeFieldVisitor");
			enumTypeVisitor.generify("T");
			JDefinedClass domainTypeVisitor = dc._interface(JMod.PUBLIC, "DomainTypeFieldVisitor");
			JTypeVar domainTypeVisitorGeneric = domainTypeVisitor.generify("T");
			domainTypeVisitor._implements(valueTypeVisitor.narrow(domainTypeVisitorGeneric));
			domainTypeVisitor._implements(entityTypeVisitor.narrow(domainTypeVisitorGeneric));
			domainTypeVisitor._implements(enumTypeVisitor.narrow(domainTypeVisitorGeneric));
			fieldVisitor = dc._interface(JMod.PUBLIC, "FieldVisitor");
			typeVisitorGeneric = fieldVisitor.generify("T");
			fieldVisitor._implements(cm.directClass("hu.mapro.model.impl.BuiltinTypeFieldVisitor").narrow(typeVisitorGeneric));
			fieldVisitor._implements(domainTypeVisitor.narrow(typeVisitorGeneric));
			typeVisitorBase = dc._class(JMod.PUBLIC|JMod.STATIC, "FieldVisitorBase");
			typeVisitorBaseGeneric = typeVisitorBase.generify("T");
			typeVisitorBase._implements(fieldVisitor.narrow(typeVisitorBaseGeneric));
			typeVisitorBase._extends(cm.directClass("hu.mapro.model.impl.BuiltinTypeFieldVisitorBase").narrow(typeVisitorBaseGeneric));
			createFieldVisitor = dc._class(JMod.PUBLIC|JMod.STATIC, "CreateFieldVisitor");
			createFieldVisitor.annotate(cm.directClass("com.google.inject.Singleton"));
			createFieldVisitorGeneric = cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.field.EditorFieldInterface").narrow(cm.wildcard());
			createFieldVisitor._implements(fieldVisitor.narrow(createFieldVisitorGeneric));
			createFieldVisitor._extends(cm.directClass("hu.mapro.gwtui.gxt.client.editor.BuiltinTypeCreateFieldVisitor"));
			
			typesClass = dc._class(JMod.STATIC|JMod.PUBLIC, "Types");
			
			fieldBaseInterface = dc._interface(JMod.PUBLIC, "_FieldBase");
			JTypeVar fbit = fieldBaseInterface.generify("T");
			JTypeVar fbiv = fieldBaseInterface.generify("V");
			fieldBaseInterface._extends(cm.ref(LabeledField.class).narrow(fbit, fbiv));
			
			fieldBaseReadbleInterface = dc._interface(JMod.PUBLIC, "_ReadableFieldBase");
			JTypeVar fbrit = fieldBaseReadbleInterface.generify("T");
			JTypeVar fbriv = fieldBaseReadbleInterface.generify("V");
			JTypeVar fbrip = fieldBaseReadbleInterface.generify("P");
			fieldBaseReadbleInterface._extends(fieldBaseInterface.narrow(fbrit, fbriv));
			fieldBaseReadbleInterface._extends(cm.ref(ReadableField.class).narrow(fbrit, fbriv, fbrip));
			
			fieldBaseReadWriteInterface = dc._interface(JMod.PUBLIC, "_ReadWriteFieldBase");
			JTypeVar fbrwit = fieldBaseReadWriteInterface.generify("T");
			JTypeVar fbrwiv = fieldBaseReadWriteInterface.generify("V");
			JTypeVar fbrwip = fieldBaseReadWriteInterface.generify("P");
			fieldBaseReadWriteInterface._extends(fieldBaseReadbleInterface.narrow(fbrwit, fbrwiv, fbrwip));
			fieldBaseReadWriteInterface._extends(cm.ref(ReadWriteField.class).narrow(fbrwit, fbrwiv, fbrwip));

			fieldBaseReadWriteSingularInterface = dc._interface(JMod.PUBLIC, "_ReadWriteSingularFieldBase");
			JTypeVar fbrwsit = fieldBaseReadWriteSingularInterface.generify("T");
			JTypeVar fbrwsiv = fieldBaseReadWriteSingularInterface.generify("V");
			fieldBaseReadWriteSingularInterface._extends(fieldBaseReadWriteInterface.narrow(fbrwsit, fbrwsiv, fbrwsiv));
			fieldBaseReadWriteSingularInterface._extends(cm.ref(SingularField.class).narrow(fbrwsit, fbrwsiv));
			
			String fieldVisitorMethodReturnVarName = "R";
			JMethod fieldBaseVisitMethod = fieldBaseInterface.method(JMod.NONE, cm.directClass(fieldVisitorMethodReturnVarName), "visit");
			JTypeVar fieldVisitorMethodReturnVar = fieldBaseVisitMethod.generify(fieldVisitorMethodReturnVarName);
			fieldBaseVisitMethod.param(fieldVisitor.narrow(fieldVisitorMethodReturnVar), "visit");
			
			
			JClass visitorFunctionInputType = fieldBaseInterface.narrow(cm.wildcard(), cm.wildcard());
			visitorFunction = createVisitorFunction(visitorFunctionInputType, fieldVisitor, fieldBaseVisitMethod, "_FieldTypeVisitorFunction");
			
			
			
			
			//autoBeanClass = cm.ref("com.google.web.bindery.autobean.shared.AutoBean");
			
			
			
			for (DefinedType bid : beanInfos) {
				
				DefinedTypeActions definedTypeActions = null;
				
				switch (bid.getTypeCategory()) {
				case ENTITY:
					definedTypeActions = new EntityTypeActions((EntityType)bid);
					break;
				case VALUE:
					definedTypeActions = new ValueTypeActions((ValueType)bid);
					break;
				case ENUMERATION:
					definedTypeActions = new EnumTypeActions((EnumType)bid);
					break;
				}
				
				definedTypeActions.perform();
				
			}
		
		}

		abstract class DefinedTypeActions {
			
			DefinedType definedType;
			Classes typeClasses;
			DefinedTypeInfo definedTypeInfo;
			JMethod getTypeCategoryMethod;

			DefinedTypeActions(DefinedType definedType) {
				super();
				this.definedType = definedType;
			}

			public void perform() throws JClassAlreadyExistsException {
			
				definedTypeInfo = extraInfo.getInfo(definedType);
				
				createTypeClasses();
				
				getTypeCategoryMethod = typeClasses.typeClass.method(JMod.PUBLIC, cm.ref(hu.mapro.model.meta.Type.TypeCategory.class), "getTypeCategory");
				
				ClassCustomizer customizer = definedTypeInfo.getCustomizer();
				if (customizer!=null) {
					customizer.customize(typeClasses.beanInterfaceClass);
				}
				
				performDefinedSubtypeSpecific();
				
				addFieldVisitorMethods(typeVisitorBase, typeClasses.domainClass, typeVisitorBaseGeneric);
				addFieldVisitorMethods(createFieldVisitor, typeClasses.domainClass, createFieldVisitorGeneric);
				
			}

			abstract void performDefinedSubtypeSpecific() throws JClassAlreadyExistsException;

			void createTypeClasses() throws JClassAlreadyExistsException {
				typeClasses = getClasses(definedType);
			}
			
		}
		
		class EnumTypeActions extends DefinedTypeActions {
			
			EnumType enumType;

			EnumTypeActions(EnumType enumType) {
				super(enumType);
				this.enumType = enumType;
			}
			
			@Override
			void createTypeClasses() throws JClassAlreadyExistsException {
				super.createTypeClasses();
				typeClasses.domainClass = cm.directClass(definedTypeInfo.getClassFullName());
			}
			
			@Override
			void performDefinedSubtypeSpecific() {
				addFieldVisitorMethods(enumTypeVisitor, typeClasses.domainClass, entityTypeVisitorGeneric);
				
				typeClasses.typeClass._implements(cm.ref(EnumType.class).narrow(typeClasses.domainClass));
				getTypeCategoryMethod.body()._return(cm.ref(TypeCategory.class).staticRef(TypeCategory.VALUE.name()));
			}
			
		}
		
		abstract class ComplexTypeActions extends DefinedTypeActions {
			
			ComplexType complexType;
			String beanName;
			JFieldVar readWriteField;
			JFieldVar readableField;
			JDefinedClass driver;
			JDefinedClass uiBuilder;
			JMethod uiBuilderConstructor;
			JDefinedClass beanFieldBaseReadWriteInterface;
			
			ComplexTypeActions(ComplexType complexType) {
				super(complexType);
				this.complexType = complexType;
			}
			
			@Override
			void performDefinedSubtypeSpecific() throws JClassAlreadyExistsException {
				//JClass autobeanType = autoBeanClass.narrow(typeClasses.beanInterfaceClass);
//				factoryCategory.param(typeClasses.visitorCategory);
				requestFactoryExtraTypes.param(typeClasses.beanInterfaceClass);
				beanName = typeClasses.propertyName;
				
//				factoryAB.method(JMod.NONE, autobeanType, beanName);
				
				JMethod factoryMethod = factory.method(JMod.NONE, typeClasses.beanInterfaceClass, beanName);
				
				JMethod factoryBCMethod = factoryByClass.method(JMod.PUBLIC, typeClasses.beanInterfaceClass, beanName);
				factoryBCMethod.body()._return(factoryByClassField.invoke("create").arg(typeClasses.beanInterfaceClass.dotclass()));
				
				
				
				JMethod factoryInitMethod = factoryInit.method(JMod.PUBLIC|JMod.STATIC, cm.VOID, "init");
				JVar factoryInitMethodInstance = factoryInitMethod.param(typeClasses.beanInterfaceClass, "object");
				
				ComplexType superClass = definedTypeInfo.getSuperClass();
				boolean hasSuperClass = superClass!=null;
				boolean isSuperClassDomain = hasSuperClass && isDomainClass(extraInfo.getInfo(superClass).getClassFullName());
				if (isSuperClassDomain) {
					Classes superClasses = getClasses(superClass);
					factoryInitMethod.body().invoke(factoryInitMethod).arg(JExpr.cast(superClasses.beanInterfaceClass, factoryInitMethodInstance));
				}
				
				JMethod fieldInterfaceMethod = typeClasses.fieldInterface.method(JMod.NONE, cm.directClass("T"), "visit");
				JTypeVar fieldInterfaceMethodGeneric = fieldInterfaceMethod.generify("T");
				fieldInterfaceMethod.param(typeClasses.fieldVisitorInterface.narrow(fieldInterfaceMethodGeneric), "visitor");
				
				typeClasses.fieldVisitorClass._implements(typeClasses.fieldVisitorInterface.narrow(typeClasses.fieldVisitorClassGeneric));
				JMethod fieldsEditorVisitorClassDefaultVisit = typeClasses.fieldVisitorClass.method(JMod.PUBLIC, typeClasses.fieldVisitorClassGeneric, "defaultVisit");
				fieldsEditorVisitorClassDefaultVisit.body()._return(JExpr._null());

				JClass editorInterfaceType = cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.field.EditorFieldInterface").narrow(cm.wildcard());
				typeClasses.beanCreateFieldVisitor._implements(typeClasses.fieldVisitorInterface.narrow(editorInterfaceType));
				JMethod beanCreateFieldVisitorConstructor = typeClasses.beanCreateFieldVisitor.constructor(JMod.PUBLIC);
				beanCreateFieldVisitorConstructor.annotate(cm.directClass("com.google.inject.Inject"));
				JFieldVar beanCreateFieldVisitorField = constructorSetField(typeClasses.beanCreateFieldVisitor, beanCreateFieldVisitorConstructor, createFieldVisitor, "createFieldVisitor");
				
				//fieldVisitorWithWildcard = fieldVisitor.narrow(cm.wildcard());
				//JClass baseClassNarrowed = fieldBaseInterface.narrow(classes.beanInterfaceClass, cm.wildcard());
				
				beanFieldBaseReadWriteInterface = dc._interface(JMod.PUBLIC, "_"+typeClasses.name+"ReadWriteFieldBase");
				JTypeVar fbrwiv = beanFieldBaseReadWriteInterface.generify("V");
				JTypeVar fbrwip = beanFieldBaseReadWriteInterface.generify("P");
				beanFieldBaseReadWriteInterface._extends(fieldBaseReadWriteInterface.narrow(typeClasses.beanInterfaceClass, fbrwiv, fbrwip));
				beanFieldBaseReadWriteInterface._extends(cm.ref(ReadWriteField.class).narrow(typeClasses.beanInterfaceClass, fbrwiv, fbrwip));
				beanFieldBaseReadWriteInterface._implements(typeClasses.fieldInterface);
				
				
				JArray allArray = JExpr.newArray(fieldBaseInterface);
				JArray readableArray = JExpr.newArray(fieldBaseReadbleInterface);
				JArray readWriteArray = JExpr.newArray(beanFieldBaseReadWriteInterface);
				
				
				for (Field pd : ((ComplexType<?>)complexType).getFields()) {
				
					JDefinedClass fieldImplClass = dc._class(
							JMod.STATIC|JMod.PUBLIC|JMod.FINAL,
							"_"+typeClasses.name+"_"+pd.getName()
					);

					JClass propertyType = getDomainPropertyType(pd);
					JClass elementClass = propertyType;
					if (pd.getCardinality()==Cardinality.SET||pd.getCardinality()==Cardinality.LIST) {
						elementClass = elementClass.getTypeParameters().get(0);
					}
					
					boolean readable = pd.isReadable();
					boolean writable = pd.isWritable();
					
					if (readable&&writable) {
						fieldImplClass._implements(beanFieldBaseReadWriteInterface.narrow(elementClass, propertyType));
					} else if (readable) {
						fieldImplClass._implements(fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, elementClass, propertyType));
					} else {
						fieldImplClass._implements(fieldBaseInterface.narrow(typeClasses.beanInterfaceClass, elementClass, propertyType));
					}
					
					if (pd.getCardinality()==Cardinality.SCALAR) {
						fieldImplClass._implements(cm.ref(SingularField.class).narrow(typeClasses.beanInterfaceClass, elementClass));
					}
					
					fieldImplClass._implements(typeClasses.fieldInterface);
					
					overrideMethod(fieldImplClass, "getName", cm.ref(String.class), JExpr.lit(pd.getName()));
					overrideMethod(fieldImplClass, "isReadable", cm.BOOLEAN, JExpr.lit(readable));
					overrideMethod(fieldImplClass, "isWritable", cm.BOOLEAN, JExpr.lit(writable));
					overrideMethod(fieldImplClass, "getCardinality", cm.ref(Cardinality.class), cm.ref(Cardinality.class).staticRef(pd.getCardinality().name()));
					
					JExpression valueTypeExpression;
					Type valueType = pd.getValueType();
					JClass valueTypeType = cm.ref(Type.class).narrow(elementClass);
					if (valueType.getTypeCategory()==TypeCategory.BUILTIN) {
						valueTypeExpression = JExpr.cast(valueTypeType, cm.ref(BuiltinTypes.class).staticRef(((BuiltinType)valueType).getBuiltinTypeCategory().name()));
					} else {
						valueTypeExpression = typesClass.staticRef(getClasses(valueType).typeField);
					}
					overrideMethod(fieldImplClass, "getValueType", valueTypeType, valueTypeExpression);
					
					overrideMethod(fieldImplClass, "getLabel", cm.ref(String.class), JExpr.lit(getReadableSymbolName(pd.getName())));
					
					
					
					FieldInfo fieldInfo = extraInfo.getInfo(pd);
					
					if (readable) {
						typeClasses.beanInterfaceClass.method(JMod.NONE, propertyType, fieldInfo.getReadMethod());
						
						fieldImplClass._implements(cm.ref(Getter.class).narrow(typeClasses.beanInterfaceClass, propertyType));
						
						JMethod fieldApply = fieldImplClass.method(JMod.PUBLIC|JMod.FINAL, propertyType, "apply");
						JVar fieldApplyObject = fieldApply.param(typeClasses.beanInterfaceClass, "object");
						fieldApply.body()._if(fieldApplyObject.eq(JExpr._null()))._then()._return(JExpr._null());
						fieldApply.body()._return(fieldApplyObject.invoke(fieldInfo.getReadMethod()));
						
						JMethod fieldGetter = fieldImplClass.method(JMod.PUBLIC|JMod.FINAL, propertyType, "get");
						JVar fieldGetterObject = fieldGetter.param(typeClasses.beanInterfaceClass, "object");
						fieldGetter.body()._if(fieldGetterObject.eq(JExpr._null()))._then()._return(JExpr._null());
						fieldGetter.body()._return(fieldGetterObject.invoke(fieldInfo.getReadMethod()));
					}
					
					if (writable) {
						JMethod setter = typeClasses.beanInterfaceClass.method(JMod.NONE, cm.VOID, fieldInfo.getWriteMethod());
						setter.param(propertyType, pd.getName());
						
						fieldImplClass._implements(cm.ref(Setter.class).narrow(typeClasses.beanInterfaceClass, propertyType));
						
						JMethod fieldSetter = fieldImplClass.method(JMod.PUBLIC|JMod.FINAL, cm.VOID, "set");
						JVar fieldSetterObject = fieldSetter.param(typeClasses.beanInterfaceClass, "object");
						JVar fieldSetterValue = fieldSetter.param(propertyType, "value");
						fieldSetter.body().invoke(fieldSetterObject, fieldInfo.getWriteMethod()).arg(fieldSetterValue);
						
						if (MetaUtils.isPluralField(pd)) {
							
							Class<?> initClass = null;
							
							if (pd.getCardinality()==Cardinality.LIST) {
								initClass = ArrayList.class;
							} else if (pd.getCardinality()==Cardinality.SET) {
								initClass = HashSet.class;
							}
							
							factoryInitMethod.body().invoke(factoryInitMethodInstance, setter).arg(JExpr._new(cm.ref(initClass).narrow(elementClass)));
						} 
						
					}
					
					typeClasses.fieldVisitorInterface.method(JMod.NONE, typeClasses.fieldVisitorInterfaceGeneric, pd.getName());
					typeClasses.fieldVisitorClass.method(JMod.PUBLIC, typeClasses.fieldVisitorInterfaceGeneric, pd.getName()).body()._return(JExpr.invoke(fieldsEditorVisitorClassDefaultVisit));

					
					JMethod fieldVisitMethod = fieldImplClass.method(JMod.PUBLIC, cm.directClass("O"), "visit");
					JTypeVar fieldVisitMethodGeneric = fieldVisitMethod.generify("O");
					JVar fieldVisitMethodParam = fieldVisitMethod.param(typeClasses.fieldVisitorInterface.narrow(fieldVisitMethodGeneric), "visitor");
					fieldVisitMethod.body()._return(fieldVisitMethodParam.invoke(pd.getName()));

					JMethod fieldTypeVisitMethod = fieldImplClass.method(JMod.PUBLIC, cm.directClass("O"), "visit");
					JTypeVar fieldTypeVisitMethodGeneric = fieldTypeVisitMethod.generify("O");
					JVar fieldTypeVisitMethodParam = fieldTypeVisitMethod.param(fieldVisitor.narrow(fieldTypeVisitMethodGeneric), "visitor");
					JBlock fieldTypeVisitMethodBody = fieldTypeVisitMethod.body();
					
					if (MetaUtils.isPluralField(pd)) {
						
						PluralField pluralField = (PluralField) pd;
						
						Classes elementClasses = getClasses(pd.getValueType());
						
						String visitorPrefix = null;
						
						if (pd.getCardinality()==Cardinality.LIST) {
							visitorPrefix = "list";
						} else if (pd.getCardinality()==Cardinality.SET) {
							visitorPrefix = "set";
						}
						
						
						fieldTypeVisitMethodBody._return(JExpr.cast(fieldTypeVisitMethodGeneric, createDomainCollectionVisitorInvocation(
								typeClasses,
								fieldTypeVisitMethodParam,
								visitorPrefix, 
								elementClasses, 
								pluralField.getInverseField()
						)));
					} else {
						fieldTypeVisitMethodBody._return(JExpr.cast(fieldTypeVisitMethodGeneric, createVisitorInvocation(
								fieldTypeVisitMethodParam,
								"scalar", 
								propertyType
						)));
					}
					
					
					JFieldVar propertyField = typeClasses.beanFieldsClass.field(
							JMod.STATIC|JMod.PUBLIC|JMod.FINAL, 
							fieldImplClass, 
							pd.getName(), 
							JExpr._new(fieldImplClass)
					);
					
					allArray.add(propertyField);
					
					if (readable) {
						readableArray.add(propertyField);
					} 
					
					if (readable&&writable) {
						readWriteArray.add(propertyField);
					}
					
					JMethod createFieldVisitMethod = typeClasses.beanCreateFieldVisitor.method(JMod.PUBLIC, editorInterfaceType, pd.getName());
					createFieldVisitMethod.body()._return(typeClasses.beanFieldsClass.staticRef(propertyField).invoke("visit").arg(beanCreateFieldVisitorField));

					
					
				}

				
				JFieldVar allField = typeClasses.beanFieldsClass.field(
						JMod.STATIC|JMod.PUBLIC|JMod.FINAL, 
						fieldBaseInterface.narrow(typeClasses.beanInterfaceClass, cm.wildcard()).array(), 
						"_all", 
						allArray
				);
				allField.annotate(SuppressWarnings.class).param("value", "unchecked");
				
				readableField = typeClasses.beanFieldsClass.field(
						JMod.STATIC|JMod.PUBLIC|JMod.FINAL, 
						fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, cm.wildcard(), cm.wildcard()).array(), 
						"_readable", 
						readableArray
						);
				readableField.annotate(SuppressWarnings.class).param("value", "unchecked");
				
				readWriteField = typeClasses.beanFieldsClass.field(
						JMod.STATIC|JMod.PUBLIC|JMod.FINAL, 
						beanFieldBaseReadWriteInterface.narrow(cm.wildcard(), cm.wildcard()).array(), 
						"_readWrite", 
						readWriteArray
						);
				//readWriteField.annotate(SuppressWarnings.class).param("value", "unchecked");
				
				
				JMethod factoryBeanMethod = factoryInit.method(JMod.PUBLIC, typeClasses.beanInterfaceClass, beanName);
				JVar factoryMethodInstance = factoryBeanMethod.body().decl(typeClasses.beanInterfaceClass, "object", factoryInitField.invoke(factoryMethod));
				factoryBeanMethod.body().invoke(factoryInitMethod).arg(factoryMethodInstance);
				factoryBeanMethod.body()._return(factoryMethodInstance);
				
				
				addToCategory(complexType, complexType);

//				JMethod vm = typeClasses.beanInterfaceVisitable.method(JMod.NONE, cm.VOID, "visit");
//				vm.param(typeClasses.visitorInterface, "visitor");
				
				JMethod vim = typeClasses.visitorInterface.method(JMod.NONE, typeClasses.visitorInterfaceGeneric, "visit");
				vim.param(typeClasses.beanInterfaceClass, "visitee");
				JMethod vvim = typeClasses.visitorVoidInterface.method(JMod.NONE, cm.VOID, "visit");
				vvim.param(typeClasses.beanInterfaceClass, "visitee");

				typeClasses.visitorVoidDelegate._implements(typeClasses.visitorInterface.narrow(cm.ref(Void.class)));
				typeClasses.visitorVoidClass._implements(typeClasses.visitorVoidInterface);
				
				typeClasses.visitorFunction._implements(cm.ref(Function.class).narrow(typeClasses.beanInterfaceClass, typeClasses.visitorFunctionGeneric));
				JClass visitorFunctionFieldType = typeClasses.visitorInterface.narrow(typeClasses.visitorFunctionGeneric);
				JFieldVar visitorFunctionField = typeClasses.visitorFunction.field(JMod.NONE, visitorFunctionFieldType, "visitor");
				JMethod visitorFunctionConstructor = typeClasses.visitorFunction.constructor(JMod.PUBLIC);
				JVar visitorFunctionConstructorParam = visitorFunctionConstructor.param(visitorFunctionFieldType, "visitor");
				visitorFunctionConstructor.body().assign(JExpr._this().ref(visitorFunctionField), visitorFunctionConstructorParam);
				JMethod visitorFunctionMethod = typeClasses.visitorFunction.method(JMod.PUBLIC, typeClasses.visitorFunctionGeneric, "apply");
				visitorFunctionMethod.annotate(cm.ref(Override.class));
				JVar visitorFunctionMethodParam = visitorFunctionMethod.param(typeClasses.beanInterfaceClass, "visitee");
				visitorFunctionMethod.body()._return(visitorsClass.staticInvoke("visit").arg(visitorFunctionMethodParam).arg(visitorFunctionField));
				
				JMethod visitorsFunctionMethod = visitorsClass.method(JMod.PUBLIC|JMod.STATIC, cm.ref(Function.class).narrow(typeClasses.beanInterfaceClass, cm.directClass("V")), "function");
				JTypeVar visitorsFunctionMethodGeneric = visitorsFunctionMethod.generify("V");
				JVar visitorsFunctionMethodParam = visitorsFunctionMethod.param(typeClasses.visitorInterface.narrow(visitorsFunctionMethodGeneric), "visitor");
				visitorsFunctionMethod.body()._return(JExpr._new(typeClasses.visitorFunction.narrow(visitorsFunctionMethodGeneric)).arg(visitorsFunctionMethodParam));
				
				JMethod visitorDelegatorMethod = typeClasses.visitorDelegatorInterface.method(JMod.NONE, cm.directClass("T"), "delegate");
				JTypeVar visitorDelegatorMethodGeneric = visitorDelegatorMethod.generify("T");
				visitorDelegatorMethod.param(typeClasses.beanInterfaceClass, "visitee");
				visitorDelegatorMethod.param(typeClasses.visitorInterface.narrow(visitorDelegatorMethodGeneric), "visitor");

				JMethod visitorsMethod = visitorsClass.method(JMod.PUBLIC|JMod.STATIC, cm.directClass("T"), "visit");
				JTypeVar visitorsMethodGeneric = visitorsMethod.generify("T");
				JVar visitee = visitorsMethod.param(typeClasses.beanInterfaceClass, "visitee");
				JVar visitor = visitorsMethod.param(typeClasses.visitorInterface.narrow(visitorsMethodGeneric), "visitor");
				visitorsMethod.body()._return(
						JExpr.invoke(
								JExpr.cast(
										typeClasses.visitorDelegatorInterface,
										visitorsClass.staticRef(visitorHierarchy).invoke("getPayload").arg(visitee)
								),
								"delegate"
						).arg(visitee).arg(visitor)
				);

				JMethod visitorsVoidMethod = visitorsClass.method(JMod.PUBLIC|JMod.STATIC, cm.VOID, "visit");
				JVar visitorsVoidMethodVisitee = visitorsVoidMethod.param(typeClasses.beanInterfaceClass, "visitee");
				JVar visitorsVoidMethodVisitor = visitorsVoidMethod.param(typeClasses.visitorVoidInterface, "visitor");
				visitorsVoidMethod.body().invoke("visit").arg(visitorsVoidMethodVisitee).arg(JExpr._new(typeClasses.visitorVoidDelegate).arg(visitorsVoidMethodVisitor));
				
				typeClasses.visitorDelegatorClass._implements(cm.directClass("hu.mapro.model.VisitorHierarchy.InstanceTester"));
				JMethod iioMethod = typeClasses.visitorDelegatorClass.method(JMod.PUBLIC, cm.BOOLEAN, "isInstanceOf");
				iioMethod.param(cm.ref(Class.class).narrow(cm.wildcard()), "clazz");
				JVar iioParam = iioMethod.param(cm.ref(Object.class), "object");
				iioMethod.body()._return(iioParam._instanceof(typeClasses.beanInterfaceClass));
				
				if (isSuperClassDomain) {
					Classes superClasses = getClasses(superClass);
					
					superClasses.visitorInterface._extends(typeClasses.visitorInterface.narrow(superClasses.visitorInterfaceGeneric));
					superClasses.visitorVoidInterface._extends(typeClasses.visitorVoidInterface);
					
					typeClasses.beanInterfaceClass._extends(superClasses.beanInterfaceClass);
					typeClasses.visitorDelegatorInterface._extends(superClasses.visitorDelegatorInterface);
					

					visitorsInit.invoke(visitorsClass.staticRef(visitorHierarchy), "link").arg(typeClasses.beanInterfaceClass.dotclass()).arg(superClasses.beanInterfaceClass.dotclass());
					
				} else {
					visitorsInit.invoke(visitorsClass.staticRef(visitorHierarchy), "root").arg(typeClasses.beanInterfaceClass.dotclass());
				}
				
				visitorsInit.invoke(visitorsClass.staticRef(visitorHierarchy), "setPayload").arg(typeClasses.beanInterfaceClass.dotclass()).arg(JExpr._new(typeClasses.visitorDelegatorClass));
				visitorsInit.invoke(visitorsClass.staticRef(visitorHierarchy), "setInstanceTester").arg(typeClasses.beanInterfaceClass.dotclass()).arg(JExpr._new(typeClasses.visitorDelegatorClass));
				
				typeClasses.visitorClass._implements(typeClasses.visitorInterface.narrow(typeClasses.visitorClassGeneric));
				typeClasses.visitorDelegatorClass._implements(typeClasses.visitorDelegatorInterface);
				
				

				JMethod getSuperTypeMethod = typeClasses.typeClass.method(JMod.PUBLIC, cm.ref(ComplexType.class).narrow(new JSuperTypeWildcard(typeClasses.beanInterfaceClass)), "getSuperType");
				getSuperTypeMethod.annotate(Override.class);
				if (complexType.getSuperType()==null) {
					getSuperTypeMethod.body()._return(JExpr._null());
				} else {
					getSuperTypeMethod.body()._return(typesClass.staticRef(getClasses(complexType.getSuperType()).typeField));
				}
				
				JMethod getFieldsMethod = typeClasses.typeClass.method(JMod.PUBLIC, cm.ref(Collection.class).narrow(cm.ref(Field.class).narrow(typeClasses.beanInterfaceClass, cm.wildcard()).wildcard()), "getFields");
				getFieldsMethod.annotate(Override.class);
				getFieldsMethod.body()._return(cm.ref(Arrays.class).staticInvoke("asList").arg(typeClasses.beanFieldsClass.staticRef(allField)));
				
				JMethod isAbstractMethod = typeClasses.typeClass.method(JMod.PUBLIC, cm.BOOLEAN, "isAbstract");
				isAbstractMethod.annotate(Override.class);
				if (complexType.isAbstract()) {
					isAbstractMethod.body()._return(JExpr.TRUE);
				} else {
					isAbstractMethod.body()._return(JExpr.FALSE);
				}
				
				
				driver = dc._interface(JMod.PUBLIC, typeClasses.name+"Driver");
				driver._implements(cm.directClass("com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver").narrow(typeClasses.beanInterfaceClass, cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.FieldsEditorComponent").narrow(typeClasses.beanInterfaceClass)));
				
				
				JClass fieldEditorClassSupertype = cm.directClass("hu.mapro.gwtui.gxt.client.ui.TypeFieldsEditor").narrow(typeClasses.beanInterfaceClass, beanFieldBaseReadWriteInterface.narrow(cm.wildcard(), cm.wildcard()));
				typeClasses.fieldsEditorClass._extends(fieldEditorClassSupertype);
				JMethod uiBuilderConstructor = typeClasses.fieldsEditorClass.constructor(JMod.PUBLIC);
				uiBuilderConstructor.annotate(cm.directClass("com.google.inject.Inject"));
				addConstructorSetProperty(uiBuilderConstructor, driver, "driver");
				addConstructorSetProperty(uiBuilderConstructor, requestFactory, "requestFactory");
				JVar createFieldVisitorParam = uiBuilderConstructor.param(typeClasses.beanCreateFieldVisitor, "createFieldVisitor");
				uiBuilderConstructor.body().invoke("setCreateFieldFunction").arg(utilClass.staticInvoke(visitorFunction).arg(createFieldVisitorParam));
				uiBuilderConstructor.body().invoke("setEditorFields").arg(typeClasses.beanFieldsClass.staticRef(readWriteField));

//				typeClasses.fieldsEditorClass._implements(cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.TypeFieldsEditor").narrow(typeClasses.beanInterfaceClass, cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.RequestContextInitializer")));
				
				JClass fieldsEditorVisitorReturnType = cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.InstanceFieldsEditor").narrow(typeClasses.beanInterfaceClass.wildcard(), cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.RequestContextInitializer"));
				typeClasses.fieldsEditorVisitorClass._extends(typeClasses.visitorClass.narrow(fieldsEditorVisitorReturnType));
				typeClasses.fieldsEditorVisitorConstructor.annotate(cm.directClass("com.google.inject.Inject"));
				
				createVisitorFunction(typeClasses.fieldInterface, typeClasses.fieldVisitorInterface, fieldInterfaceMethod, "_"+beanName+"FieldVisitorFunction");
				
				complexUiBuilder();
				
				performComplexSubtypeSpecific();
				
			}

			abstract void performComplexSubtypeSpecific() throws JClassAlreadyExistsException;
			
			void complexUiBuilder() throws JClassAlreadyExistsException {
				JDefinedClass columnModel = dc._interface(JMod.PUBLIC, typeClasses.name+"ColumnModel");
				columnModel._implements(cm.directClass("hu.mapro.gwtui.gxt.client.columnmodel.ColumnModelProvider").narrow(typeClasses.beanInterfaceClass));

				JDefinedClass defaultColumnModel = dc._class(JMod.PUBLIC|JMod.STATIC, typeClasses.name+"DefaultColumnModel");
				defaultColumnModel._implements(columnModel);
				defaultColumnModel._extends(cm.directClass("hu.mapro.gwtui.gxt.client.columnmodel.ColumnModelBuilder").narrow(
						typeClasses.beanInterfaceClass, 
						fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, cm.wildcard(), cm.wildcard())
				));

				JMethod defaultColumnModelConstructor = defaultColumnModel.constructor(JMod.PUBLIC);
				defaultColumnModelConstructor.annotate(cm.directClass("com.google.inject.Inject"));
				defaultColumnModelConstructor.body().invoke("setTableColumns").arg(typeClasses.beanFieldsClass.staticRef(readableField));
				
				columnModel.annotate(cm.directClass("com.google.inject.ImplementedBy")).param("value", defaultColumnModel);
				
				uiBuilder = dc._class(JMod.PUBLIC|JMod.STATIC, typeClasses.name+"UiBuilder");
				
				uiBuilderConstructor = uiBuilder.constructor(JMod.PUBLIC);
				uiBuilderConstructor.annotate(cm.directClass("com.google.inject.Inject"));
				addConstructorSetProperty(uiBuilderConstructor, "hu.mapro.gwtui.client.login.LoginService", "loginService");
				addConstructorSetProperty(uiBuilderConstructor, "hu.mapro.gwtui.client.MessageInterface", "messageInterface");
				addConstructorSetProperty(uiBuilderConstructor, "hu.mapro.gwtui.client.HasPages", "hasPages");
				addConstructorSetProperty(uiBuilderConstructor, columnModel, "columnModel");
				
				JVar fieldsEditorVisitorParam = uiBuilderConstructor.param(typeClasses.fieldsEditorVisitorClass, "fieldsEditorVisitor");
				uiBuilderConstructor.body().invoke("setEditorFunction").arg(visitorsClass.staticInvoke("function").arg(fieldsEditorVisitorParam));

				
				uiBuilderConstructor.body().invoke("setEntityClass").arg(typeClasses.beanInterfaceClass.dotclass());
				uiBuilderConstructor.body().invoke("setPageTitle").arg(JExpr.lit(getReadableSymbolName(beanName)+" Management"));
				uiBuilderConstructor.body().invoke("setGridHeader").arg(JExpr.lit(getReadableSymbolName(beanName)+" Items"));
				uiBuilderConstructor.body().invoke("setEditorHeader").arg(JExpr.lit(getReadableSymbolName(beanName)+" Details"));
				
//				uiBuilderConstructor.body().invoke("setTableColumns").arg(metaUtils.staticInvoke("getReadableFields").arg(typesClass.staticRef(classes.typeField)));
//				uiBuilderConstructor.body().invoke("setEditorFields").arg(metaUtils.staticInvoke("getReadWriteFields").arg(typesClass.staticRef(classes.typeField)));

				
			}
			
		}
		
		class ValueTypeActions extends ComplexTypeActions {
			
			ValueType valueType;
			
			ValueTypeActions(ValueType valueType) {
				super(valueType);
				this.valueType = valueType;
			}
			
			@Override
			void performComplexSubtypeSpecific() throws JClassAlreadyExistsException {
				JAnnotationUse proxyForAnnotation = typeClasses.beanInterfaceClass.annotate(cm.directClass("com.google.web.bindery.requestfactory.shared.ProxyFor"));
				proxyForAnnotation.param("value", cm.directClass(definedTypeInfo.getClassFullName()));
				
				typeClasses.beanInterfaceClass._implements(cm.directClass("com.google.web.bindery.requestfactory.shared.ValueProxy"));
				
				
				JDefinedClass requestFactoryAdapter = dc._interface(JMod.PUBLIC, typeClasses.name+"RequestFactoryAdapter");
				requestFactoryAdapter._implements(
						cm.directClass("hu.mapro.gwtui.gxt.client.recordseditor.requestfactory.Interface").narrow(typeClasses.beanInterfaceClass)
				);
				
				JClass domainUiBuilder = cm.directClass("hu.mapro.gwtui.gxt.client.ui.ValueRecordsEditor");
				uiBuilder._extends(domainUiBuilder.narrow(
						typeClasses.beanInterfaceClass,
						fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, cm.ref(Comparable.class).narrow(cm.wildcard()).wildcard(), cm.ref(Comparable.class).narrow(cm.wildcard()).wildcard()),
						//fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, cm.wildcard(), cm.wildcard()),
						beanFieldBaseReadWriteInterface.narrow(cm.wildcard(), cm.wildcard())
				));
				
				addConstructorSetProperty(uiBuilderConstructor, requestFactoryAdapter, "requestFactoryAdapter");
				
				
				
				addFieldVisitorMethods(valueTypeVisitor, typeClasses.beanInterfaceClass, valueTypeVisitorGeneric);

				typeClasses.typeClass._implements(cm.ref(ValueType.class).narrow(typeClasses.beanInterfaceClass));
				
				getTypeCategoryMethod.body()._return(cm.ref(TypeCategory.class).staticRef(TypeCategory.VALUE.name()));
			}
			
		}
		
		class EntityTypeActions extends ComplexTypeActions {
			
			EntityType entityType;
			
			EntityTypeActions(EntityType entityType) {
				super(entityType);
				this.entityType = entityType;
			}
			
			@Override
			void performComplexSubtypeSpecific() throws JClassAlreadyExistsException {
				JAnnotationUse proxyForAnnotation = typeClasses.beanInterfaceClass.annotate(cm.directClass("com.google.web.bindery.requestfactory.shared.ProxyFor"));
				proxyForAnnotation.param("value", cm.directClass(definedTypeInfo.getClassFullName()));
				proxyForAnnotation.param("locator", cm.directClass("hu.mapro.gwtui.server.data.LongIdLocator"));

				typeClasses.beanInterfaceClass._implements(cm.directClass("hu.mapro.model.LongId"));
				
				
				typeClasses.beanInterfaceClass._implements(cm.directClass("com.google.web.bindery.requestfactory.shared.EntityProxy"));
				
				JDefinedClass requestContext = dc._interface(JMod.PUBLIC, typeClasses.name+"RequestContext");
				requestContext._implements(cm.directClass("com.google.web.bindery.requestfactory.shared.RequestContext"));
				JClass domainRequestMethods = cm.directClass("hu.mapro.gwtui.client.data.EntityRequestMethods");
				requestContext._implements(domainRequestMethods.narrow(typeClasses.beanInterfaceClass));
				
				JClass request = cm.directClass("com.google.web.bindery.requestfactory.shared.Request");
				
				requestContext.method(JMod.NONE, request.narrow(cm.ref(Void.class)), "persist").param(typeClasses.beanInterfaceClass, "object");
				requestContext.method(JMod.NONE, request.narrow(cm.ref(Void.class)), "remove").param(typeClasses.beanInterfaceClass, "object");
				requestContext.method(JMod.NONE, request.narrow(typeClasses.beanInterfaceClass), "merge").param(typeClasses.beanInterfaceClass, "object");
				requestContext.method(JMod.NONE, request.narrow(cm.ref(List.class).narrow(typeClasses.beanInterfaceClass)), "list");
				
				JAnnotationUse serivceAnnotation = requestContext.annotate(
						cm.ref("com.google.web.bindery.requestfactory.shared.Service")
				);
				serivceAnnotation.param("value", cm.ref(serviceClass.fullName()+"."+typeClasses.name+"Service"));
				serivceAnnotation.param("locator", cm.ref("hu.mapro.gwtui.server.data.GwtServiceLocator"));
				
				JMethod rfMethod = requestFactory.method(JMod.NONE, requestContext, beanName);

				JDefinedClass requestContextFactory = dc._class(JMod.PUBLIC|JMod.STATIC, typeClasses.name+"RequestContextFactory");
				requestContextFactory.annotate(cm.directClass("com.google.inject.Singleton"));
				JClass supplier = cm.directClass("com.google.common.base.Supplier");
				requestContextFactory._implements(supplier.narrow(requestContext));
				JFieldVar rfField = requestContextFactory.field(JMod.NONE, requestFactory, "requestFactory");
				
				JMethod rfConstructor = requestContextFactory.constructor(JMod.PUBLIC);
				rfConstructor.annotate(cm.directClass("com.google.inject.Inject"));
				JVar rfConstructorParam = rfConstructor.param(requestFactory, "requestFactory");
				rfConstructor.body().assign(JExpr._this().ref(rfField), rfConstructorParam);
				
				requestContextFactory.method(JMod.PUBLIC, requestContext, "get").body()._return(JExpr.invoke(rfField, rfMethod));
				
	
				//uiBuilder.annotate(cm.directClass("com.google.inject.Singleton"));
				JClass domainUiBuilder = cm.directClass("hu.mapro.gwtui.gxt.client.ui.EntityRecordsEditor");
				uiBuilder._extends(domainUiBuilder.narrow(
						typeClasses.beanInterfaceClass,
						fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, cm.ref(Comparable.class).narrow(cm.wildcard()).wildcard(), cm.ref(Comparable.class).narrow(cm.wildcard()).wildcard()),
						//fieldBaseReadbleInterface.narrow(typeClasses.beanInterfaceClass, cm.wildcard(), cm.wildcard()),
						beanFieldBaseReadWriteInterface.narrow(cm.wildcard(), cm.wildcard())
				));
				
				addConstructorSetProperty(uiBuilderConstructor, requestContextFactory, "requestContextFactory");
	
				addFieldVisitorMethods(entityTypeVisitor, typeClasses.beanInterfaceClass, entityTypeVisitorGeneric);
				
				typeClasses.typeClass._implements(cm.ref(EntityType.class).narrow(typeClasses.beanInterfaceClass));
				
				getTypeCategoryMethod.body()._return(cm.ref(TypeCategory.class).staticRef(TypeCategory.ENTITY.name()));
			}
			
		}
		
		private void overrideMethod(JDefinedClass implClass,
				String methodName, 
				JType returnType,
				JExpression returnValue
		) {
			JMethod method = implClass.method(JMod.PUBLIC, returnType, methodName);
			method.annotate(Override.class);
			method.body()._return(returnValue);
		}

		private JClass getDomainPropertyType(Field pd) throws JClassAlreadyExistsException {
			if (pd.getCardinality()==Cardinality.LIST) {
				return cm.ref(List.class).narrow(getScalarDomainPropertyType(pd));
			} else if (pd.getCardinality()==Cardinality.SET) {
				return cm.ref(Set.class).narrow(getScalarDomainPropertyType(pd));
			} else {
				return getScalarDomainPropertyType(pd);
			}
		}

		private JClass getScalarDomainPropertyType(Field pd) throws JClassAlreadyExistsException {
			Type valueType = pd.getValueType();
			
			return getDomainType(valueType);
		}

		private JClass getDomainType(Type valueType)
				throws JClassAlreadyExistsException {
			if (valueType.getTypeCategory()==TypeCategory.BUILTIN || valueType.getTypeCategory()==TypeCategory.ENUMERATION) {
				return cm.directClass(extraInfo.getInfo(valueType).getClassFullName());
			} else {
				return getClasses(valueType).beanInterfaceClass;
			}
		}
		
		private void addFieldVisitorMethods(
				JDefinedClass visitorType,
				JClass beanInterfaceClass,
				JClass returnType
		) {
			String typeName = beanInterfaceClass.name();
			JMethod scalarMethod = visitorType.method(JMod.PUBLIC, returnType, "scalar"+typeName);
			JMethod listMethod = visitorType.method(JMod.PUBLIC, returnType, "list"+typeName);
			JMethod setMethod = visitorType.method(JMod.PUBLIC, returnType, "set"+typeName);
			addCollectionMethodParam(listMethod, beanInterfaceClass);
			addCollectionMethodParam(setMethod, beanInterfaceClass);
			
			if (!visitorType.isInterface()) {
				scalarMethod.body()._return(JExpr.invoke("defaultValue"));
				listMethod.body()._return(JExpr.invoke("defaultValue"));
				setMethod.body()._return(JExpr.invoke("defaultValue"));
			}
		}

		private void addCollectionMethodParam(
				JMethod listMethod,
				JClass beanInterfaceClass
		) {
			JTypeVar fromType = listMethod.generify("O");
			
			JMultiTypeVarBound b = new JMultiTypeVarBound(cm, "X");
			b.bound(cm.ref(SingularField.class).narrow(beanInterfaceClass, fromType));
			b.bound(cm.ref(Setter.class).narrow(beanInterfaceClass, fromType));
			
			JTypeVar inverseType = listMethod.generify("I").bound(b);
			listMethod.param(inverseType, "inverseField");
		}

		private JInvocation createVisitorInvocation(
				JVar fieldVisitParam,
				String methodPrefix,
				JClass elementClass 
		) {
			return fieldVisitParam.invoke(methodPrefix+getScalarName(elementClass));
		}

		private JInvocation createDomainCollectionVisitorInvocation(
				Classes typeClasses, JVar fieldVisitParam,
				String methodPrefix,
				Classes elementClasses, 
				SingularField singularField 
		) throws JClassAlreadyExistsException {
			// TODO check if inverse property is in superclass
			// TODO manage modules
			JInvocation invocation = fieldVisitParam.invoke(methodPrefix+elementClasses.name);
			if (singularField==null) {
				invocation.arg(JExpr.cast(fieldBaseReadWriteSingularInterface.narrow(elementClasses.beanInterfaceClass, typeClasses.beanInterfaceClass), JExpr._null()));
			} else {
				invocation.arg(
						elementClasses.beanFieldsClass.staticRef(singularField.getName())
				);
			}
			return invocation;
		}
		

		private String getScalarName(JClass elementClass) {
			return elementClass.name();
		}


		private void addToCategory(ComplexType toClass, ComplexType fromClass) throws JClassAlreadyExistsException {
			
			DefinedTypeInfo toClassInfo = extraInfo.getInfo(toClass);
			DefinedTypeInfo fromClassInfo = extraInfo.getInfo(fromClass);
			
			String toClassFullName = toClassInfo.getClassFullName();
			String fromClassFullName = fromClassInfo.getClassFullName();
			
			Classes toClasses = getClasses(toClass);
			Classes fromClasses = getClasses(fromClass);
			
			
			
//			JMethod vmi = toClasses.visitorCategory.method(JMod.PUBLIC|JMod.STATIC, cm.VOID, "visit");
//			JClass autobeanType = autoBeanClass.narrow(toClasses.beanInterfaceClass);
//			JVar instanceParam = vmi.param(autobeanType, "instance");
//			JVar visitorParam = vmi.param(fromClasses.visitorInterface, "visitor");
//			vmi.body().invoke(visitorParam, "visit").arg(instanceParam.invoke("as"));
			
			JMethod vdmi = toClasses.visitorDelegatorClass.method(JMod.PUBLIC, cm.directClass("T"), "delegate");
			JTypeVar vdmiGeneric = vdmi.generify("T");
			JVar vdiInstanceParam = vdmi.param(fromClasses.beanInterfaceClass, "visitee");
			JVar vdiVisitorParam = vdmi.param(fromClasses.visitorInterface.narrow(vdmiGeneric), "visitor");
			vdmi.body()._return(JExpr.invoke(vdiVisitorParam, "visit").arg(JExpr.cast(toClasses.beanInterfaceClass, vdiInstanceParam)));
			
			
//			visitorsClass.staticRef(fromClasses.visitorDelegateMap).invoke("put").arg(toClasses.beanInterfaceClass).arg()
			
			
			JMethod vicm = fromClasses.visitorClass.method(JMod.PUBLIC, fromClasses.visitorClassGeneric, "visit");
			JVar visitee = vicm.param(toClasses.beanInterfaceClass, "visitee");
			
			if (!toClassFullName.equals(fromClassFullName)) {
				vicm.body()._return(JExpr.invoke("visit").arg(JExpr.cast(getClasses(toClassInfo.getSuperClass()).beanInterfaceClass, visitee)));
			} else {
				vicm.body()._return(JExpr._null());
			}
			
			JMethod vimv = fromClasses.visitorVoidDelegate.method(JMod.PUBLIC|JMod.FINAL, cm.ref(Void.class), "visit");
			JVar vimvParam = vimv.param(toClasses.beanInterfaceClass, "visitee");
			vimv.body().invoke(fromClasses.visitorVoidDelegateField, "visit").arg(vimvParam);
			vimv.body()._return(JExpr._null());
			
			
			JMethod vicmv = fromClasses.visitorVoidClass.method(JMod.PUBLIC, cm.VOID, "visit");
			JVar visiteev = vicmv.param(toClasses.beanInterfaceClass, "visitee");
			
			if (!toClassFullName.equals(fromClassFullName)) {
				vicmv.body().add(JExpr.invoke("visit").arg(JExpr.cast(getClasses(toClassInfo.getSuperClass()).beanInterfaceClass, visiteev)));
			}
			
			JFieldVar fieldsEditorVisitorClassField = fromClasses.fieldsEditorVisitorClass.field(JMod.NONE, toClasses.fieldsEditorClass, toClasses.propertyName);
			JVar fieldsEditorVisitorConstructorParam = fromClasses.fieldsEditorVisitorConstructor.param(toClasses.fieldsEditorClass, toClasses.propertyName);
			fromClasses.fieldsEditorVisitorConstructor.body().assign(JExpr._this().ref(fieldsEditorVisitorClassField), fieldsEditorVisitorConstructorParam);
			JClass fieldsEditorVisitorReturnType = cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.InstanceFieldsEditor").narrow(toClasses.beanInterfaceClass, cm.directClass("hu.mapro.gwtui.gxt.client.fieldseditor.RequestContextInitializer"));
			JMethod fieldsEditorVisitorClassMethod = fromClasses.fieldsEditorVisitorClass.method(JMod.PUBLIC, fieldsEditorVisitorReturnType, "visit");
			JVar fieldsEditorVisitorClassMethodParam = fieldsEditorVisitorClassMethod.param(toClasses.beanInterfaceClass, "visitee");
			fieldsEditorVisitorClassMethod.body()._return(JExpr._this().ref(fieldsEditorVisitorClassField).invoke("edit").arg(fieldsEditorVisitorClassMethodParam));
			
			ComplexType superType = fromClass.getSuperType();
			
			DefinedTypeInfo superInfo = extraInfo.getInfo(superType);
			if (superType!=null && isDomainClass(superInfo.getClassFullName())) {
				addToCategory(toClass, superType);
			}
			
		}
		
		
		boolean isDomainClass(String beanClass) {
			return domainClasses.contains(beanClass);
		}

		class Classes {
			String classFullName;
			String name;
			String propertyName;
			
			JClass domainClass;
			JDefinedClass beanInterfaceClass;
//			JDefinedClass beanInterfaceVisitable;
			
			JDefinedClass fieldInterface;
			JDefinedClass fieldVisitorInterface;
			JDefinedClass fieldVisitorClass;
			
			JDefinedClass visitorInterface;
			JDefinedClass visitorClass;
			JDefinedClass visitorVoidDelegate;
			JDefinedClass visitorVoidInterface;
			JDefinedClass visitorVoidClass;
//			JDefinedClass visitorCategory;
			JDefinedClass visitorDelegatorInterface;
			JDefinedClass visitorDelegatorClass;
			JDefinedClass visitorFunction;
			
			JDefinedClass beanFieldsClass;
			
			JDefinedClass fieldsEditorClass;
			JDefinedClass fieldsEditorVisitorClass;
			
			private JDefinedClass typeClass;
			private JFieldVar typeField;
			private JTypeVar visitorInterfaceGeneric;
			private JTypeVar visitorClassGeneric;
			private JTypeVar visitorFunctionGeneric;
			private JMethod fieldsEditorVisitorConstructor;
			private JFieldVar visitorVoidDelegateField;
			private JTypeVar fieldVisitorInterfaceGeneric;
			private JTypeVar fieldVisitorClassGeneric;
			private JDefinedClass beanCreateFieldVisitor;
			
			
			
			Classes(String classFullName, boolean isEnum) throws JClassAlreadyExistsException {
				this.classFullName = classFullName;
				
				domainClass = cm.directClass(classFullName);
				this.name = domainClass.name();
				this.propertyName = StringUtils.uncapitalize(name);
				
				if (!isEnum) {
					beanInterfaceClass = dc._interface(
							JMod.PUBLIC, 
							name
							);
					domainClass = beanInterfaceClass;
					
//					beanInterfaceVisitable = dc._interface(
//							JMod.PUBLIC, 
//							name+"Visitable"
//							);
					visitorInterface = dc._interface(
							JMod.PUBLIC, 
							name+"Visitor"
							);
					visitorInterfaceGeneric = visitorInterface.generify("T");
					
					visitorClass = dc._class(
							JMod.PUBLIC|JMod.STATIC, 
							name+"VisitorBase"
							);
					visitorClassGeneric = visitorClass.generify("T");
					visitorVoidDelegate = dc._class(
							JMod.PUBLIC|JMod.STATIC, 
							name+"VoidDelegateVisitor"
					);
					visitorVoidInterface = dc._interface(
							JMod.PUBLIC, 
							name+"VoidVisitor"
							);
					visitorVoidDelegateField = constructorSetProperty(
							visitorVoidDelegate, visitorVoidInterface,"voidVisitor");

					visitorVoidClass = dc._class(
							JMod.PUBLIC|JMod.STATIC, 
							name+"VoidVisitorBase"
							);
					visitorFunction = dc._class(
							JMod.PUBLIC|JMod.STATIC, 
							name+"VisitorFunction"
					);
					visitorFunctionGeneric = visitorFunction.generify("V");
					
					
//					visitorCategory = dc._class(
//							JMod.PUBLIC|JMod.STATIC, 
//							name+"Category"
//							);
					
					visitorDelegatorInterface = dc._interface(
							JMod.PUBLIC, 
							name+"VisitorDelegator"
							);
					visitorDelegatorClass = dc._class(
							JMod.PUBLIC|JMod.STATIC, 
							name+"VisitorDelegatorImpl"
							);
					
					beanFieldsClass = dc._class(
							JMod.STATIC|JMod.PUBLIC|JMod.FINAL, 
							name+"Fields"
							);
					fieldsEditorClass = dc._class(
							JMod.STATIC|JMod.PUBLIC, 
							name+"FieldsEditor"
					);
					fieldsEditorVisitorClass = dc._class(
							JMod.STATIC|JMod.PUBLIC, 
							name+"FieldsEditorVisitor"
					);
					fieldsEditorVisitorConstructor = fieldsEditorVisitorClass.constructor(JMod.PUBLIC);
					
					fieldInterface = dc._interface(
							JMod.PUBLIC, 
							name+"Field"
					);
					fieldVisitorInterface = dc._interface(
							JMod.PUBLIC, 
							name+"FieldVisitor"
					);
					fieldVisitorInterfaceGeneric = fieldVisitorInterface.generify("T");
					fieldVisitorClass = dc._class(
							JMod.STATIC|JMod.PUBLIC, 
							name+"FieldVisitorBase"
					);
					fieldVisitorClassGeneric = fieldVisitorClass.generify("T");
					beanCreateFieldVisitor = dc._class(
							JMod.STATIC|JMod.PUBLIC, 
							name+"CreateFieldVisitor"
					);

				}
				
				typeClass = dc._class(
						JMod.STATIC|JMod.PUBLIC|JMod.FINAL, 
						"_"+name+"_Type"
				);

				
				typeField = typesClass.field(JMod.PUBLIC|JMod.STATIC|JMod.FINAL, typeClass, propertyName, JExpr._new(typeClass));
				
				classesInterfaceMap.put(domainClass.fullName(), this);
			}
		}
		
		private JClassContainer dc;
		private JDefinedClass utilClass;
		Map<String, Classes> classesMap = Maps.newHashMap();
		Map<String, Classes> classesInterfaceMap = Maps.newHashMap();
		private JDefinedClass requestFactory;
		private JDefinedClass fieldVisitor;
		//private JClass fieldVisitorWithWildcard;
		private JDefinedClass createFieldVisitor;
		private JMethod visitorFunction;
		private JDefinedClass entityTypeVisitor;
		private JTypeVar entityTypeVisitorGeneric;
		private JDefinedClass valueTypeVisitor;
		private JDefinedClass enumTypeVisitor;
		private JTypeVar valueTypeVisitorGeneric;
		private JTypeVar typeVisitorGeneric;
		private JDefinedClass typeVisitorBase;
		private JTypeVar typeVisitorBaseGeneric;
		private JClass createFieldVisitorGeneric;
		
		Classes getClasses(Type type) throws JClassAlreadyExistsException { 
			String classFullName = extraInfo.getInfo(type).getClassFullName();
			Classes classes = classesMap.get(classFullName);
			
			if (classes==null) {
				classes = new Classes(classFullName, type.getTypeCategory()==TypeCategory.ENUMERATION);
				
				classesMap.put(classFullName, classes);
			}
			
			return classes;
		}
		
		Classes getClassesForInterface(JClass beanInterfaceClass) throws JClassAlreadyExistsException { 
			return classesInterfaceMap.get(beanInterfaceClass.fullName());
		}
		
		JMethod createVisitorFunction(
				JClass visitorFunctionInputType,
				JClass fieldVisitor,
				JMethod fieldBaseVisitMethod,
				String functionClassName
		) throws JClassAlreadyExistsException {
			String visitorFunctionReturnVarName = "R";
			JClass visitorFunctionReturnType = cm.ref(Function.class).narrow(visitorFunctionInputType, cm.directClass(visitorFunctionReturnVarName));
			JMethod visitorFunction = utilClass.method(JMod.PUBLIC|JMod.STATIC, visitorFunctionReturnType, "visitorFunction");
			JTypeVar visitorFunctionReturnVar = visitorFunction.generify(visitorFunctionReturnVarName);
			JClass visitorFunctionParamType = fieldVisitor.narrow(visitorFunctionReturnVar);
			JVar visitorFunctionParam = visitorFunction.param(JMod.FINAL, visitorFunctionParamType, "visitor");
			JDefinedClass visitorFunctionClass = dc._class(JMod.STATIC, functionClassName);
			visitorFunctionClass.generify(visitorFunctionReturnVarName);
			visitorFunctionClass._implements(visitorFunctionReturnType);
			JFieldVar visitoFunctionClassField = visitorFunctionClass.field(JMod.NONE, visitorFunctionParamType, "visitor");
			JMethod visitorFunctionClassConstructor = visitorFunctionClass.constructor(JMod.NONE);
			JVar visitorFunctionClassConstructorParam = visitorFunctionClassConstructor.param(visitorFunctionParamType, "visitor");
			visitorFunctionClassConstructor.body().assign(JExpr._this().ref(visitoFunctionClassField), visitorFunctionClassConstructorParam);
			JMethod applyMethod = visitorFunctionClass.method(JMod.PUBLIC, visitorFunctionReturnVar, "apply");
			JVar visitorFunctionInputParam = applyMethod.param(visitorFunctionInputType, "field");
			applyMethod.body()._return(visitorFunctionInputParam.invoke(fieldBaseVisitMethod).arg(visitoFunctionClassField));
			JClass returnObjectClass = visitorFunctionClass.narrow(visitorFunctionReturnVar);
			visitorFunction.body()._return(JExpr._new(returnObjectClass).arg(visitorFunctionParam));
			return visitorFunction;
		}
		
	}
	
	private void doGenerate(String packageName, String className,
			Annotater annotater, JClass serviceClass,
			Collection<DefinedType<?>> beanInfos, ExtraInfo extraInfo) {
		try {
			
			JDefinedClass dc = cm._class(
					JMod.PUBLIC|JMod.FINAL, 
					packageName+"."+className,
					ClassType.CLASS
			);
			if (annotater!=null) {
				annotater.annotate(dc);
			}

			new Generation(dc, serviceClass, beanInfos, extraInfo).doGenerate();
			
		} catch (JClassAlreadyExistsException e) {
			throw new RuntimeException(e);
		}
	}

    public static String getReadableSymbolName(final String camelCase) {
        final Pattern p = Pattern.compile("[A-Z][^A-Z]*");
        final Matcher m = p.matcher(StringUtils.capitalize(camelCase));
        final StringBuilder builder = new StringBuilder();
        while (m.find()) {
            builder.append(m.group()).append(" ");
        }
        return builder.toString().trim();
    }
    
    public void addConstructorSetProperty(
    		JMethod constructor,
    		String type,
    		String propertyName
    ) {
    	JClass typeJClass = cm.directClass(type);
		addConstructorSetProperty(constructor, typeJClass, propertyName);
    }

	private JVar addConstructorSetProperty(JMethod constructor,
			JClass typeJClass, String propertyName) {
		JVar param = constructor.param(typeJClass, propertyName);
    	constructor.body().invoke(getMutatorMethodName(propertyName)).arg(param);
    	return param;
	}
    
    public static JFieldVar constructorSetProperty(JDefinedClass definedClass, JClass fieldType, String fieldName) {
		JMethod visitorVoidDelegateConstructor = definedClass.constructor(JMod.PUBLIC);
		return constructorSetField(definedClass, visitorVoidDelegateConstructor,
				fieldType, fieldName);
	}

	public static JFieldVar constructorSetField(JDefinedClass definedClass, JMethod constructor, JClass fieldType,
			String fieldName) {
		JVar visitorVoidDelegateConstructorParam = constructor.param(fieldType, fieldName);
		JFieldVar visitorVoidDelegateField = definedClass.field(JMod.NONE, fieldType, fieldName);
		constructor.body().assign(JExpr._this().ref(visitorVoidDelegateField), visitorVoidDelegateConstructorParam);
		return visitorVoidDelegateField;
	}

	public static String getMutatorMethodName(
            final String fieldName) {
        return "set"
                + StringUtils.capitalize(fieldName);
    }
    
//	private void createVisitorFunction(JCodeModel cm) throws JClassAlreadyExistsException {
//		JClass visitorFunctionInputType = null;
//		JDefinedClass dc = null;
//		JClass fieldVisitor = null;
//		JMethod fieldBaseVisitMethod = null;
//		
//		createVisitorFunction(cm, dc, visitorFunctionInputType, fieldVisitor,
//				fieldBaseVisitMethod);
//		
//	}

	public void generateFields(
			JClassContainer classContainer,
			JClass serviceClass,
			Collection<DefinedType<?>> beanInfos, 
			ExtraInfo extraInfo
	) {
		try {
			new Generation(classContainer, serviceClass, beanInfos, extraInfo).doGenerate();
		} catch (JClassAlreadyExistsException e) {
			throw new RuntimeException(e);
		}
	}
}
